//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <winhack.h>
#include <elasys_server.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include "CBasicDirectory.h"
#include "CBasicFile.h"
#include "CDirStrEnumerator.h"
#include "CFileAttr.h"

#define Lock()   do{EnterCriticalSection(&m_FileLock);}while(0)
#define Unlock() do{LeaveCriticalSection(&m_FileLock);}while(0)

extern RTL_CRITICAL_SECTION DirLock;
UInt32 CBasicDirectory::AddRef()
{
    LONG nRef = m_cRef.Increment();
    return (ULONG)nRef;
}

UInt32 CBasicDirectory::Release()
{
    LONG nRef = m_cRef.Decrement();

    if (0 == nRef) {
        delete this;
    }
    return nRef;
}

PInterface CBasicDirectory::Probe(REIID riid)
{
    if (EIID_IInterface == riid) {
        return (IInterface *)(IBasicDirectory *)this;
    }
    else if (EIID_IBasicFile == riid) {
        return (IBasicFile *)(IBasicDirectory *)this;
    }
    else if (EIID_IBasicDirectory == riid) {
        return (IBasicDirectory *)this;
    }
    else if (EIID_IFileMonitor == riid) {
        return (IFileMonitor *)this;
    }
    else if (EIID_CLASS_INFO == riid) {
        return (IInterface *)&ECLSID_CKtfsDirectory;
    }
    else if (EIID_INTERFACE_INFO == riid) {
        return (IInterface *)&EIID_IBasicDirectory;
    }

    return NULL;
}

CBasicDirectory::CBasicDirectory()
{
    m_wstrCurDir = NULL;
}

#ifdef _GNUC
const UINT64 bias64 = 0x019db1ded53e8000LL;
//1970.01.01 - 1601.01.01
#else
const UINT64 bias64 = 0x019db1ded53e8000;
//1970.01.01 - 1601.01.01
#endif

CBasicDirectory::CBasicDirectory(WString esDir)
{
    wchar_t *wcsCurDir = (wchar_t *) malloc(sizeof(wchar_t)
                                            * (wcslen(esDir)+1));
    assert(wcsCurDir);
    wcscpy(wcsCurDir, esDir);
    m_wstrCurDir = wcsCurDir;
    m_eFMode = FileAccessMode_ReadWrite;

    InitializeCriticalSection(&m_FileLock);

    //init time attributes of dir
    m_DirHandle = CreateFileW(m_wstrCurDir, GENERIC_READ,
                              FILE_SHARE_READ | FILE_SHARE_WRITE,
                              NULL, OPEN_ALWAYS,
                              FILE_FLAG_BACKUP_SEMANTICS, NULL);
    if (m_DirHandle == INVALID_HANDLE_VALUE) {
        printf("Dir %S handle creation failed, error code: %d\n",
               m_wstrCurDir, GetLastError());
        printf("All time attributes are set to 0!\n");
        m_iCreateTime = 0;
        m_iAccessTime = 0;
        m_iModificationTime = 0;
        return;
    }

    FILETIME CreationTime;
    FILETIME LastAccessTime;
    FILETIME LastWriteTime;
    UINT64 tmpint;

    GetFileTime(m_DirHandle, &CreationTime,
                &LastAccessTime, &LastWriteTime);

    tmpint = *(UINT64*)&CreationTime;
    m_iCreateTime = (UINT32)((tmpint - bias64) / 10000000);
    tmpint = *(UINT64*)&LastAccessTime;
    m_iAccessTime = (UINT32)((tmpint - bias64) / 10000000);
    tmpint = *(UINT64*)&LastWriteTime;
    m_iModificationTime = (UINT32)((tmpint - bias64) / 10000000);

}

CBasicDirectory::~CBasicDirectory()
{
    if (m_wstrCurDir) {
        free((void *)m_wstrCurDir);
    }
    if (m_DirHandle != INVALID_HANDLE_VALUE) {
        CloseHandle(m_DirHandle);
    }
    DeleteCriticalSection(&m_FileLock);
}

ECode CBasicDirectory::GetNameStat(
    wchar_t *wcsName, BOOL *bIsExist, BOOL *bIsDir)
{
    DWORD attr = GetFileAttributesW(wcsName);

    if (attr == 0xffffffff) {
        *bIsExist = FALSE;
        return S_FALSE;
    }
    else
        *bIsExist = TRUE;

    if (attr & FILE_ATTRIBUTE_DIRECTORY) {
        *bIsDir = TRUE;
    }
    else {
        *bIsDir = FALSE;
    }

    return NOERROR;
}

ECode CBasicDirectory::CombineName(
    WString name, wchar_t *fullname)
{
    assert(m_wstrCurDir);
    wchar_t *wcsName = fullname;
    wcscat(wcsName, m_wstrCurDir);
    wcscat(wcsName, L"\\");
    wcscat(wcsName, name);
    return NOERROR;
}

ECode CBasicDirectory::Aggregate(
    AggregateType type, PInterface pObj)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicDirectory::GetDomain(
    IInterface **ppObj)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicDirectory::Sync()
{
    // TODO: Add your code here
    return NOERROR;
}

ECode CBasicDirectory::GetType(
    /* [out] */ FileType * pType)
{
    if (!pType) {
        return E_INVALID_ARGUMENT;
    }

    *pType = FileType_Directory;
    return NOERROR;
}

ECode CBasicDirectory::GetAccessTime(
    /* [out] */ INT * pTime)
{
    if (!pTime) {
        return E_INVALID_ARGUMENT;
    }

    *pTime = m_iAccessTime;
    return NOERROR;
}

ECode CBasicDirectory::SetAccessTime(
    /* [in] */ INT time)
{
    if (m_eFMode == FileAccessMode_Read) {
        return E_FS_NO_PERMISSION;
    }
    if (time < MIN_TIME_VALUE) {
        return E_INVALID_ARGUMENT;
    }

    m_iAccessTime = time;
    return NOERROR;
}

ECode CBasicDirectory::GetModificationTime(
    /* [out] */ INT * pTime)
{
    if (!pTime) {
        return E_INVALID_ARGUMENT;
    }

    *pTime = m_iModificationTime;
    return NOERROR;
}

ECode CBasicDirectory::SetModificationTime(
    /* [in] */ INT time)
{
    if (time < MIN_TIME_VALUE) {
        return E_INVALID_ARGUMENT;
    }
    if (m_eFMode == FileAccessMode_Read) {
        return E_FS_NO_PERMISSION;
    }
    m_iModificationTime = time;
    return NOERROR;
}

ECode CBasicDirectory::GetCreateTime(
    /* [out] */ INT * pTime)
{
    if (!pTime) {
        return E_INVALID_ARGUMENT;
    }

    *pTime = m_iCreateTime;
    return NOERROR;
}

ECode CBasicDirectory::CheckAccessMode(
    /* [in] */ FileAccessMode mode)
{
    if ((mode != FileAccessMode_Read)
        && (mode != FileAccessMode_Write)
        && (mode != FileAccessMode_ReadWrite)) {
        return E_INVALID_ARGUMENT;
    }

    ECode ec = NOERROR;

    Lock();
    if ((mode == FileAccessMode_Write || mode == FileAccessMode_ReadWrite)
       && (m_eFMode == FileAccessMode_Read)) {
        ec = E_FS_NO_PERMISSION;
    }
    Unlock();
    return ec;

}

ECode CBasicDirectory::GetAccessMode(
    /* [out] */ FileAccessMode * pMode)
{
    if (!pMode) {
        return E_INVALID_ARGUMENT;
    }

    *pMode = m_eFMode;
    if (m_eFMode == FileAccessMode_Write) {
        *pMode = FileAccessMode_ReadWrite;
    }

    return NOERROR;
}

ECode CBasicDirectory::SetAccessMode(
    /* [in] */ FileAccessMode mode)
{
    if (mode != FileAccessMode_Read
       && mode != FileAccessMode_Write
       && mode != FileAccessMode_ReadWrite) {
        return E_INVALID_ARGUMENT;
    }
    wchar_t *wcsName = m_wstrCurDir;
    DWORD attr = GetFileAttributesW(wcsName);
    if (mode == FileAccessMode_Read) {
        attr |= FILE_ATTRIBUTE_READONLY;
    }
    else {
        attr &= ~FILE_ATTRIBUTE_READONLY;
    }
    SetFileAttributesW(wcsName, attr);
    m_eFMode = mode;
    return NOERROR;
}

ECode CBasicDirectory::GetShareMode(
    /* [out] */ FileShareMode * pMode)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicDirectory::SetShareMode(
    /* [in] */ FileShareMode mode)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicDirectory::Lookup(
    /* [in] */ WString name,
    /* [out] */ IBasicFile ** piBasicFile)
{
    if (name.IsNullOrEmpty()
        ||wcspbrk((const wchar_t *)name, wszIllegal) != NULL
        || piBasicFile == NULL
        || name.GetLength() > (_MAX_PATH - 5) //MAX_PATH is 260
        || (name.GetLength() + wcslen(m_wstrCurDir)) >= _MAX_PATH) {
        return E_INVALID_ARGUMENT;
    }

    FileAttr *pFileAttr = NULL;
    ECode ec = NOERROR;
    *piBasicFile = NULL;

    BOOL bIsExist = FALSE, bIsDir = FALSE;
    wchar_t wcsName[_MAX_PATH] = {0};
    CombineName(name, wcsName);
    GetNameStat(wcsName, &bIsExist, &bIsDir);

    if (!bIsExist) {
        return E_FS_NO_SUCH_FILE;
    }
    else if (bIsDir) {//dir
        CBasicDirectory *pBasicDirectory = new CBasicDirectory((WString)wcsName);
        if (pBasicDirectory == NULL) {
            return E_OUT_OF_MEMORY;
        }
        if (GetFileAttributesW(wcsName) & FILE_ATTRIBUTE_READONLY)
            pBasicDirectory->SetAccessMode(FileAccessMode_Read);
        pBasicDirectory->AddRef();
        *piBasicFile = (IBasicFile *)pBasicDirectory;
        return NOERROR;
    }
    else {//file
        CBasicFile *pBasicFile = new CBasicFile((WString)wcsName, TRUE, FALSE);
        if (pBasicFile == NULL) {
            return E_OUT_OF_MEMORY;
        }

        ec = GetFileAttr(wcsName, pBasicFile, &pFileAttr);
        if (FAILED(ec)) {
            delete pBasicFile;
        	return ec;
        }

        pBasicFile->SetFileAttr(pFileAttr);
        pBasicFile->AddRef();

        if (GetFileAttributesW(wcsName) & FILE_ATTRIBUTE_READONLY)
            pBasicFile->SetAccessMode(FileAccessMode_Read);

        *piBasicFile = (IBasicFile *)pBasicFile;
        return NOERROR;
    }
}

ECode CBasicDirectory::CreateFile(
    /* [in] */ WString name,
    /* [in] */ FileAccessMode cmode,
    /* [in] */ Boolean bExclusive,
    /* [out] */ IBasicFile ** piBasicFile)
{
    if (name.IsNullOrEmpty()) return E_INVALID_ARGUMENT;
    WStringBuf_<_MAX_PATH> ws;
    name = name.TrimEnd(ws);
    if (name.IsNullOrEmpty()
        || NULL == piBasicFile || cmode < 0 || cmode > 2
        || wcspbrk((const wchar_t *)name, wszIllegal) != NULL
        || name.GetLength() > (_MAX_PATH - 5) //MAX_PATH is 260
        || (name.GetLength() + wcslen(m_wstrCurDir)) >= _MAX_PATH) {
        return E_INVALID_ARGUMENT;
      }

    *piBasicFile = NULL;
    ECode ec = NOERROR;
    CBasicFile *pBasicFile = NULL;
    FileAttr *pFileAttr = NULL;
    wchar_t wcsName[_MAX_PATH] = {0};//_MAX_PAHT is 260
    BOOL bIsExist = FALSE, bIsDir = FALSE;

    CombineName(name, wcsName);
    GetNameStat(wcsName, &bIsExist, &bIsDir);

    if (bIsExist) {
        if (bExclusive) {
            return E_FS_FILE_EXISTS;
        }
        else if (bIsDir) {
            return E_FS_IS_DIRECTORY;
        }
    }

    if (!bIsExist) {
        pBasicFile = new CBasicFile((WString)wcsName, FALSE, TRUE);
    }
    else if (!bExclusive) {
        if (cmode != FileAccessMode_Read) {
            //file exist and open with write access
            pBasicFile = new CBasicFile((WString)wcsName, TRUE, TRUE);
        }
        else {//file exist and open with read access
            pBasicFile = new CBasicFile((WString)wcsName, TRUE, FALSE);
        }
    }

    if (pBasicFile == NULL)
        return E_OUT_OF_MEMORY;
    if (!pBasicFile->GetFileHandle(NULL)) {
        delete pBasicFile;
        pBasicFile = NULL;
        return E_INVALID_OPERATION;
    }

    ec = GetFileAttr(wcsName, pBasicFile, &pFileAttr);
    if (FAILED(ec)) {
        delete pBasicFile;
        return ec;
    }

    pBasicFile->AddRef();
    pBasicFile->SetFileAttr(pFileAttr);
    pBasicFile->SetAccessMode(cmode);
    *piBasicFile = (IBasicFile *)pBasicFile;
    return NOERROR;
}

ECode CBasicDirectory::DeleteFile(
    /* [in] */ WString name)
{
    if (name.IsNullOrEmpty()) return E_INVALID_ARGUMENT;
    WStringBuf_<_MAX_PATH> ws;
    name = name.TrimEnd(ws);
    
    if (name.IsNullOrEmpty()
        || wcspbrk((const wchar_t *)name, wszIllegal) != NULL
        || name.GetLength() > (_MAX_PATH - 5) //MAX_PATH is 260
        || (name.GetLength() + wcslen(m_wstrCurDir)) >= _MAX_PATH)
        return E_INVALID_ARGUMENT;

    DWORD att = 0;
    ECode ec = NOERROR;
    BOOL bIsExist = FALSE, bIsDir = FALSE;
    wchar_t wcsName[_MAX_PATH] = {0};

    CombineName(name, wcsName);
    LockList(DirLock);
    GetNameStat(wcsName, &bIsExist, &bIsDir);
    if (!bIsExist) {
        ec = E_FS_NO_SUCH_FILE;
        goto exit2;
    }
    if (bIsDir) {
        ec = E_FS_IS_DIRECTORY;
        goto exit2;
    }

    att = GetFileAttributesW(wcsName);
    if (att & FILE_ATTRIBUTE_READONLY) {
        ec = E_FS_NO_PERMISSION;
        goto exit2;
    }

    BOOL ret = ::DeleteFileW(wcsName);
    if (!ret) {
        ec = E_FS_FILE_BUSY;
        goto exit2;
    }
    else {
        RemoveFromFileAttriList(wcsName);
    }

exit2:
    UnlockList(DirLock);
    return ec;
}

ECode CBasicDirectory::Rename(
    /* [in] */ WString oldName,
    /* [in] */ IBasicDirectory * pNewDir,
    /* [in] */ WString newName)
{
    if (oldName.IsNullOrEmpty()
        || newName.IsNullOrEmpty()
        || NULL == pNewDir
        || wcspbrk((const wchar_t *)newName, wszIllegal) != NULL
        || newName.GetLength() > (_MAX_PATH - 5) //MAX_PATH is 260
        || (newName.GetLength() + wcslen(m_wstrCurDir)) >= _MAX_PATH) {
        return E_INVALID_ARGUMENT;
    }

    BOOL bOldNameExist, bOldNameIsDir;
    BOOL bNewNameExist, bNewNameIsDir;
    wchar_t wcsOldName[_MAX_PATH] = {0};
    wchar_t wcsNewName[_MAX_PATH] = {0};
    ECode ec = NOERROR;

    CombineName(oldName, wcsOldName);
    ((CBasicDirectory *)pNewDir)->CombineName(newName, wcsNewName);

    LockList(DirLock);
    GetNameStat(wcsOldName, &bOldNameExist, &bOldNameIsDir);
    GetNameStat(wcsNewName, &bNewNameExist, &bNewNameIsDir);

    if (!bOldNameExist) {
        //oldname doesnt exist
        ec = E_FS_NO_SUCH_FILE;
        goto exit1;
    }
    if (wcscmp(wcsOldName, wcsNewName) == 0) {
        goto exit1;
    }
    else if (_wcsicmp(wcsOldName, wcsNewName) == 0) {
        bNewNameExist = FALSE;
        goto exit1;
    }
    else if (bOldNameExist && bOldNameIsDir &&
        bNewNameExist && !bNewNameIsDir) {
        //oldname is dir and newname is file that has already exist
       ec = E_FS_FILE_EXISTS;
       goto exit1;
    }
    else if (bOldNameExist && !bOldNameIsDir &&
        bNewNameExist && bNewNameIsDir) {
        //oldname is file and newname is dir that has already exist
        ec = E_FS_FILE_EXISTS;
        goto exit1;
    }
    else if (bNewNameExist) {
        //newname exist
        ec = E_FS_FILE_EXISTS;
        goto exit1;
    }

    BOOL ret = MoveFileW(wcsOldName, wcsNewName);
    if (!ret) {
        ec = E_INVALID_ARGUMENT;
        goto exit1;
    }

exit1:
    UnlockList(DirLock);
    return ec;
}

ECode CBasicDirectory::CreateDirectory(
    /* [in] */ WString name,
    /* [in] */ FileAccessMode mode)
{
    if (name.IsNullOrEmpty()) return E_INVALID_ARGUMENT;
    WStringBuf_<_MAX_PATH> ws;
    name = name.TrimEnd(ws);
    if ( name.IsNullOrEmpty()
       ||(mode != FileAccessMode_Read
           &&mode != FileAccessMode_Write
           && mode != FileAccessMode_ReadWrite)
       || wcspbrk((const wchar_t *)name, wszIllegal) != NULL
       || name.GetLength() > (_MAX_PATH - 5) //MAX_PATH is 260
       || (name.GetLength() + wcslen(m_wstrCurDir)) >= _MAX_PATH) {
        return E_INVALID_ARGUMENT;
        }

    BOOL bIsExist = FALSE, bIsDir = FALSE;
    //Extern the length of window's file name
    wchar_t wcsName[_MAX_PATH+4] = {L'\\', L'\\', L'\?', L'\\', 0};
    CombineName(name, wcsName+4);
    GetNameStat(wcsName, &bIsExist, &bIsDir);
    if (bIsExist && bIsDir) {
        return E_FS_FILE_EXISTS;
    }

    BOOL ret = CreateDirectoryW(wcsName, NULL);
    if (!ret) {
        return E_INVALID_OPERATION;
    }

    if (mode == FileAccessMode_Read) {
        DWORD attr = GetFileAttributesW(wcsName);
        attr |= FILE_ATTRIBUTE_READONLY;
        SetFileAttributesW(wcsName, attr);
    }

    return NOERROR;
}

ECode CBasicDirectory::DeleteDirectory(
    /* [in] */ WString name)
{
    if (name.IsNullOrEmpty()) return E_INVALID_ARGUMENT;
    WStringBuf_<_MAX_PATH> ws;
    name = name.TrimEnd(ws);
    
    if (name.IsNullOrEmpty()
        || wcspbrk((const wchar_t *)name, wszIllegal) != NULL) {
        return E_INVALID_ARGUMENT;
    }
    if (name.Compare(L".") == 0) {
        return E_FS_FILE_BUSY;
    }
    if (name.Compare(L"..") == 0) {
        return E_FS_NO_PERMISSION;
    }

    BOOL bIsExist = FALSE, bIsDir = FALSE;
    ECode ec = NOERROR;
    wchar_t wcsName[_MAX_PATH] = {0};
    CombineName(name, wcsName);
    Lock();
    GetNameStat(wcsName, &bIsExist, &bIsDir);
    if (!bIsExist) {
        ec = E_FS_NO_SUCH_FILE;
        goto exit0;
    }
    else if (!bIsDir){
        ec = E_FS_NOT_DIRECTORY;
        goto exit0;
    }

    BOOL ret = RemoveDirectoryW(wcsName);
    if (!ret) {
        int err = GetLastError();
        if (145 == err || 32 == err) {
        	//ERROR_DIR_NOT_EMPTY  ERROR_SHARING_VIALATION
            ec = E_FS_FILE_BUSY;
            goto exit0;
        }
        else {
            ec = E_INVALID_OPERATION;
            goto exit0;
        }
    }
    
exit0:
    Unlock();
    return ec;
}

ECode CBasicDirectory::GetDirEntries(
    /* [out] */ IWStringEnumerator ** piEntryNameEnumerator)
{
    if (!piEntryNameEnumerator) return E_INVALID_ARGUMENT;
    CDirStrEnumerator *pDirStrEnum = NULL;
    wchar_t wcsPath[_MAX_PATH] = {0};
    wcscat(wcsPath, (const wchar_t *)m_wstrCurDir);
    wcscat(wcsPath, L"\\*.*");
    pDirStrEnum = new CDirStrEnumerator(wcsPath);
    if (!pDirStrEnum) return E_OUT_OF_MEMORY;

    pDirStrEnum->AddRef();
    *piEntryNameEnumerator = pDirStrEnum;
    return NOERROR;
}

ECode CBasicDirectory::AddFileMonitor(
    /* [out] */ IEvent **pEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CBasicDirectory::RemoveFileMonitor(
    /* [in] */ IEvent *pEvent)
{
    return E_NOT_IMPLEMENTED;
}
